package za.org.foobar.famon.reporter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import za.org.foobar.famon.dto.CFailure;
import za.org.foobar.famon.dto.CLoggingEvent;

import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class FamonThreadPoolWorkProcessor implements FamonWorkProcessor {
    private final Log log = LogFactory.getLog(getClass());
    private int threadPoolCount = 1;

    private FamonReporterLogic logic;
    private final ArrayList<Work> workQueue = new ArrayList<Work>();
    private Worker threadPool[];

    public void setFamonReporterLogic(FamonReporterLogic logic) {
        this.logic = logic;
    }

    public int getThreadPoolCount() {
        return threadPoolCount;
    }

    public void setThreadPoolCount(int threadPoolCount) {
        log.debug("threadPoolCount = \"" + threadPoolCount + "\"");
        this.threadPoolCount = threadPoolCount;
    }

    public void sendFailure(CFailure failure, String threadName, List<CLoggingEvent> loggingEventList,
                            Throwable exception) {
        addWork(new Work(failure, threadName, loggingEventList, exception));
    }
    
    private void addWork(Work work) {
        synchronized (workQueue) {
            if (threadPool == null) {
                threadPool = new Worker[threadPoolCount];
            }
            for (int i = 0; i < threadPool.length; i++) {
                if (threadPool[i] == null || threadPool[i].isFree) {
                    if (log.isDebugEnabled()) {
                        log.debug("Thread[" + i + "] is free, starting work now.");
                    }
                    if (threadPool[i] == null) {
                        threadPool[i] = new Worker();
                    }
                    threadPool[i].startWork(work);
                    Thread thread = new Thread(threadPool[i]);
                    thread.start();
                    return;
                }
            }
            workQueue.add(work);
            if (log.isDebugEnabled()) {
                log.debug("All " + threadPool.length + " threads in the pool are busy, queueing work. There is now a " +
                        "total of " + workQueue.size() + " items on the queue.");
            }
        }
    }

    private Work getWork(Worker worker) {
        synchronized (workQueue) {
            if (workQueue.size() > 0) {
                if (log.isDebugEnabled()) {
                    log.debug("Thread is grabbing an item of work of " + workQueue.size() + " total items on the queue");
                }
                return workQueue.remove(0);
            }
            log.debug("Thread could not find any work on the queue");
            worker.stopWork();
            return null;
        }
    }

    private class Worker implements Runnable {
        private Work work;
        public boolean isFree;

        public void startWork(Work work) {
            this.work = work;
            isFree = false;
        }

        public void stopWork() {
            isFree = true;
        }

        public void run() {
            while (work != null) {
                work.sendFailure(logic);
                work = getWork(this);
            }
        }
    }

    private static class Work {
        private CFailure failure;
        private String threadName;
        private List<CLoggingEvent> loggingEventList;
        private Throwable exception;

        private Work(CFailure failure, String threadName, List<CLoggingEvent> loggingEventList,
                                  Throwable exception) {
            this.failure = failure;
            this.threadName = threadName;
            this.loggingEventList = loggingEventList;
            this.exception = exception;
        }

        public void sendFailure(FamonReporterLogic logic) {
            logic.populateFailure(failure, threadName, loggingEventList, exception);
            logic.sendFailure(failure);
        }
    }

}
